package emu.decvices.apu;

import emu.decvices.bus.BusDevice;
import emu.decvices.bus.IRQLine;
import emu.decvices.bus.IRQLineDevice;
import emu.decvices.clock.ClockDevice;
import emu.decvices.cpu.CPU;
import emu.decvices.cpu.DmaDevice;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import java.io.Serializable;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class APU extends BusDevice implements ClockDevice {
    private CPU cpu;//引用CPU，APU本身就是CPU的一部分
    /**
     * CPU工作频率常量
     *
     * @value 1, 789, 773 Hz
     * @description 表示系统CPU的固定工作频率，即CPU每秒钟执行1,789,773个周期。
     */
    private static final int CPU_FREQUENCY_NTSC = 1789773;
//    private static final int APU_FREQUENCY_NTSC = CPU_FREQUENCY_NTSC>>1;
    /**
     * 帧计数器（Frame Counter）是用于同步音频系统中各个部分的操作的定时器。它的频率通常被设定为240 Hz
     */
    private int frameCounterRate = 240;
    private int frameCounter;
    /**
     * 方波通道1
     */
    private static final int PulseChannel_1 = 1;
    /**
     * 方波通道2
     */
    private static final int PulseChannel_2 = 1;

    // 定义长度表（lengthTable），用于存储APU一系列内置的音频持续时长的长度值。
    private static int[] lengthTable = {10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30};

    // 空占比                                          0.125                     0.25                     0.5                       0.75
//    private static final int[][] dutyTable = {{0, 0, 0, 0, 0, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 1, 1}, {0, 0, 0, 0, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 0, 0}};
    /*
        Implementation details
        The reason for the odd output from the sequencer is that the counter is initialized to zero but counts downward rather than upward. Thus it reads the sequence lookup table in the order 0, 7, 6, 5, 4, 3, 2, 1.

        实施细节
        顺序器的输出之所以奇怪，是因为计数器的初始化值为零，但它是向下计数，而不是向上计数。因此，它按照 0、7、6、5、4、3、2、1 的顺序读取序列查找表。
     */
    private static final int[][] dutyTable = {{0, 1, 0, 0, 0, 0, 0, 0}, {0, 1, 1, 0, 0, 0, 0, 0}, {0, 1, 1, 1, 1, 0, 0, 0}, {1, 0, 0, 1, 1, 1, 1, 1}};

    /**
     * 音频采样率常量
     *
     * @value 44100 Hz
     * @description 表示系统进行音频数据采样的固定频率，即每秒采集44100个样本。
     */
//    private static final int sampleRate = 44100;
    private static final int sampleRate = 22050;

    private static final int fourStepMode = 0;//四步模式
    private static final int fiveStepMode = 1;//五步模式

    private int mode;//帧计数器工作模式，4步模式还是5步模式

    private boolean frameCountInterruptInhibitFlag;//帧计数器的中断抑制标值

    private PulseChannel pulseChannel1 = new PulseChannel(PulseChannel_1);//方波通道1
    private PulseChannel pulseChannel2 = new PulseChannel(PulseChannel_2);//方波通道2
    private TriangleChannel triangleChannel = new TriangleChannel();//三角波通道
    private NoiseChannel noiseChannel = new NoiseChannel();//噪声通道
    private DMCChannel dmcChannel = new DMCChannel();//DMC通道
    private transient SourceDataLine line;

    private boolean clockRunning = true;

    private IRQLineDevice irqDevice = new IRQLineDevice();

    public APU(CPU cpu) {
        this.cpu = cpu;
        frameCounterFrequency = CPU_FREQUENCY_NTSC / frameCounterRate;
        sampleRateFrequency = CPU_FREQUENCY_NTSC / sampleRate;
        IRQLine.getInstance().addDevice(irqDevice);
        start();
    }

    public void start() {
        clockRunning = true;
        if (line == null) {
            try {
                AudioFormat format = new AudioFormat(sampleRate, 8, 1, false, false);
                line = AudioSystem.getSourceDataLine(format);
                line.open();
                line.start();
            } catch (LineUnavailableException e) {
                throw new RuntimeException(e);
            }
        }
        //APU时钟，单独为APU提供时钟，否则会因为PPU,CPU时钟拖慢导致采样率不足出现爆音
        Thread ct = new Thread(new Clock());
        ct.setDaemon(true);
        ct.start();
        //播放线程
        Thread pt = new Thread(() -> {
            while (clockRunning) {
                byte[] data = sampleQueue.poll();
                if (data != null) {
                    line.write(data, 0, data.length);
                }
                try {
                    Thread.sleep(0, 10);//sleep 1微秒
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        pt.setDaemon(true);
        pt.setPriority(Thread.MAX_PRIORITY);
        pt.start();
    }

    public void apuShutdown() {
        clockRunning = false;
    }

    private class Clock implements Runnable {
        private static final long NANOS_PER_SECOND = 1000000000;// 一秒钟有多少纳秒
        private static final long NANOS_PER_CYCLE = NANOS_PER_SECOND / CPU_FREQUENCY_NTSC;

        @Override
        public void run() {
            long lastTime = System.nanoTime();// 获取当前时间的纳秒值，用于计算循环的时间差
            long accumulatedTime = 0;//累计时间
            long currentTime;
            long deltaTime;
            while (clockRunning) { // 主循环
                currentTime = System.nanoTime();//当前时间
                deltaTime = currentTime - lastTime;//时间差
                lastTime = currentTime;
                accumulatedTime += deltaTime;//累计时间增加

                // 根据累积时间运行 CPU 周期
                while (clockRunning && accumulatedTime >= NANOS_PER_CYCLE) {
                    clock();
                    accumulatedTime -= NANOS_PER_CYCLE;// 减去已模拟的 CPU 周期时间

                }
                // 同步真实时间（可选的睡眠以防止CPU 100%占用
                long sleepTime = NANOS_PER_CYCLE - accumulatedTime;
                if (sleepTime > 0) {
                    try {
                        Thread.sleep(sleepTime / 1000000, (int) (sleepTime % 1000000));
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }

            }
        }
    }

    public void playSample() {
        int pulse1 = 0;
        int pulse2 = 0;
        int triangle = 0;
        int noise = 0;
        int dmc = 0;
        if (pulseChannel1.isChannelEnabled()) {
            //方波通道1
            pulse1 = pulseChannel1.nextSampling();
        }
        if (pulseChannel2.isChannelEnabled()) {
            //方波通道1
            pulse2 = pulseChannel2.nextSampling();
        }
        if (triangleChannel.isChannelEnabled()) {
            triangle = triangleChannel.nextSampling();
        }
        if (noiseChannel.isChannelEnabled()) {
            noise = noiseChannel.nextSampling();
        }
        if (dmcChannel.isChannelEnabled()) {
            dmc = dmcChannel.nextSampling();
        }
        double sample = mix(pulse1, pulse2, triangle, noise, dmc);
        addSample((byte) (sample * 256));
    }

    private BlockingQueue<byte[]> sampleQueue = new LinkedBlockingQueue<>();//采样队列
    private int sampleArrayLength = sampleRate * 2 / 100;//20ms的采样
    private byte[] sampleArray = new byte[sampleArrayLength];//20ms的采样数据
    private int sampleArrayIndex = 0;

    /**
     * 收集20ms的采样数据后添加到队列，等待Player线程播放
     *
     * @param sample
     */
    private void addSample(byte sample) {
        if (sampleArrayIndex == sampleArray.length) {
            sampleArrayIndex = 0;
            sampleQueue.offer(sampleArray);
            sampleArray = new byte[sampleArrayLength];
        }
        sampleArray[sampleArrayIndex++] = sample;
    }

    /**
     * 混合不同音频信号的函数
     *
     * @param pulse1   第一个脉冲音频信号的强度
     * @param pulse2   第二个脉冲音频信号的强度
     * @param triangle 三角波音频信号的强度
     * @param noise    噪声音频信号的强度
     * @param dmc      DMC（Digital Microphone Connector）音频信号的强度
     * @return 混合后音频信号的强度
     */
    public double mix(double pulse1, double pulse2, double triangle, double noise, double dmc) {

        // 计算脉冲音频信号的混合输出
        double pulse_out = 0.00752 * (pulse1 + pulse2);

        // 计算三角波、噪声和DMC音频信号的混合输出
        double tnd_out = 0.00851 * triangle + 0.00494 * noise + 0.00335 * dmc;
        // 返回脉冲和三角波、噪声、DMC音频信号混合后的总输出
        return pulse_out + tnd_out;

//        double pulse_out = 0;
//        if(pulse1>0&&pulse2>0) {
//            pulse_out = 95.88 / (8128 / (pulse1 + pulse2) + 100);
//        }
//
//        double tnd_out = 159.79/(1/(triangle/8227+noise/12241+dmc/22638)+100);
//        return pulse_out+tnd_out;
    }

    @Override
    public int fromDeviceRead(int address) {
        switch (address) {
            case 0x4015:
                int d = 0;
                d |= pulseChannel1.playing() ? 1 : 0;
                d |= (pulseChannel2.playing() ? 1 : 0) << 1;
                d |= (triangleChannel.playing() ? 1 : 0) << 2;
                d |= (noiseChannel.playing() ? 1 : 0) << 3;
                d |= (dmcChannel.playing() ? 1 : 0) << 4;
                d |= irqDevice.needIRQ() ? 1 : 0 << 6;
                d |= dmcMemoryReader.dmcIirqDevice.needIRQ() ? 1 : 0 << 7;
                irqDevice.acknowledge();
                return d;
        }
        return -1;
    }

    @Override
    public boolean toDeviceWrite(int address, int data) {
        switch (address) {
            case 0x4000://方波通道1的包络设置
                pulseChannel1.setDutyAndVolumeByEnvelope(data);
                return true;
            case 0x4001:
                pulseChannel1.setSweep(data);
                return true;
            case 0x4002:
                pulseChannel1.setTimer(data, true);//计数器的低八位
                return true;
            case 0x4003:
                pulseChannel1.setTimer(data, false);//计数器的高3位
                pulseChannel1.setLengthCounter(data);
                return true;
            case 0x4004://方波通道2的包络设置
                pulseChannel2.setDutyAndVolumeByEnvelope(data);
                return true;
            case 0x4005:
                pulseChannel2.setSweep(data);
                return true;
            case 0x4006:
                pulseChannel2.setTimer(data, true);//计数器的低八位
                return true;
            case 0x4007:
                pulseChannel2.setTimer(data, false);//计数器的高3位
                pulseChannel2.setLengthCounter(data);
                return true;
            case 0x4008:
                triangleChannel.setLineCount(data);
                return true;
            case 0x400A:
                triangleChannel.setTimer(data, true);//计数器的低八位
                return true;
            case 0x400B:
                triangleChannel.setTimer(data, false);
                triangleChannel.setLengthCounter(data);
                return true;
            case 0x400C:
                noiseChannel.setVolumeByEnvelope(data);
                return true;
            case 0x400E:
                noiseChannel.setPeriod(data);
                return true;
            case 0x400F:
                noiseChannel.setLengthCounter(data);
                return true;
            case 0x4010:
                //IL--.RRRR
                if ((data & 0x80) > 0) {
                    dmcMemoryReader.dmcIirqDevice.irqEnable();
                } else {
                    dmcMemoryReader.dmcIirqDevice.irqDisable();
                }
                dmcChannel.loop = (data & 0x40) > 0;
                dmcChannel.rateIndex = (data & 0x0F);
                dmcChannel.timer = 0;
                return true;
            case 0x4011:
                /*
                    $4011	-DDD.DDDD	Direct load (write)
                    bits 6-0	-DDD.DDDD	The DMC output level is set to D, an unsigned value. If the timer is outputting a clock at the same time, the output level is occasionally not changed properly.[1]
                 */
                dmcChannel.outLevel = data & 0x7F;
                return true;
            case 0x4012:
                /*
                    $4012	AAAA.AAAA	Sample address (write)
                    bits 7-0	AAAA.AAAA	Sample address = %11AAAAAA.AA000000 = $C000 + (A * 64)
                 */
                dmcMemoryReader.sampleAddress = 0xC000 + (data << 6);
                return true;
            case 0x4013:
                /*
                    $4013	LLLL.LLLL	Sample length (write)
                    bits 7-0	LLLL.LLLL	Sample length = %LLLL.LLLL0001 = (L * 16) + 1 bytes
                 */
                dmcMemoryReader.sampleLength = (data << 4) + 1;
                return true;
            case 0x4015:
                pulseChannel1.setChannelEnabled((data & 0x01) > 0);
                pulseChannel2.setChannelEnabled((data & 0x02) > 0);
                triangleChannel.setChannelEnabled((data & 0x04) > 0);
                noiseChannel.setChannelEnabled((data & 0x08) > 0);
                dmcChannel.setChannelEnabled((data & 0x10) > 0);
                return true;
            case 0x4017:
                mode = (data & 0x80) >> 7;
                frameCountInterruptInhibitFlag = (data & 0x40) > 0;
                if (frameCountInterruptInhibitFlag) {
                    irqDevice.irqDisable();
                } else {
                    irqDevice.irqEnable();
                }
                return true;
        }
        return false;
    }

    private int cycleCount;
    private int frameCounterFrequency;
    private int sampleRateFrequency;

    @Override
    public void clock() {
        cycleCount++;
        if(dmcMemoryReader.isTransfer(cycleCount)){
            dmcMemoryReader.transfer();
        }
        if ((cycleCount & 0x01) == 0) {
            pulseChannel1.clock();
            pulseChannel2.clock();
            noiseChannel.clock();
        }
        dmcChannel.clock();
        triangleChannel.clock();
        //帧计数器240Hz
        if (cycleCount % frameCounterFrequency == 0) {
            processFrameCounter();
        }
        //保持sampleRate播放采样
        if (cycleCount % sampleRateFrequency == 0) {
            playSample();
        }
    }


    /**
     * 处理帧计数器，工作频率240HZ
     * 4 步模式	   5 步模式	    功能
     * - - - f	   - - - - -	产生中断
     * - l - l	   - l - - l	驱动长度计数器（Length counter）和扫描单元（Sweep）
     * e e e e	   e e e - e	驱动包络（Envelope）与线性计数器（Linear counter）
     */
    private void processFrameCounter() {
        if (mode == fourStepMode) {//4步模式
            switch (frameCounter % 4) {
                case 1:
                    processLengthCounterAndSweep();
                    break;
                case 3:
                    irqDevice.irq();
                    processLengthCounterAndSweep();

            }
            processEnvelopeAndLineCounter();
        } else {//5步模式
            switch (frameCounter % 5) {
                case 0:
                case 2:
                    processEnvelopeAndLineCounter();
                    break;
                case 1:
                case 4:
                    processLengthCounterAndSweep();
                    processEnvelopeAndLineCounter();
                    break;
            }
        }
        frameCounter++;
    }

    /**
     * 处理所有通道的长度计数器和方波通道的滑音
     */
    private void processLengthCounterAndSweep() {
        pulseChannel1.processLengthCounter();
        pulseChannel2.processLengthCounter();
        noiseChannel.processLengthCounter();
        triangleChannel.processLengthCounter();

        pulseChannel1.processSweep();//处理滑音
        pulseChannel2.processSweep();
    }

    /**
     * 处理包络和线性计数器
     */
    private void processEnvelopeAndLineCounter() {
        pulseChannel1.processEnvelope();//处理包络
        pulseChannel2.processEnvelope();
        noiseChannel.processEnvelope();
        //处理三角波的线性计数器
        triangleChannel.processLineCounter();
    }


    private class DMCChannel extends Chanel {
        private boolean loop = false;//循环输出
        private int rateIndex;//采样率下标，对应采样率表
        /*
            $4011	-DDD.DDDD	Direct load (write)
            bits 6-0	-DDD.DDDD	The DMC output level is set to D, an unsigned value. If the timer is outputting a clock at the same time, the output level is occasionally not changed properly.[1]
         */
        private int outLevel = 0;//输出级别 对应$4011

        //----------------Output Unit-----------------
        /*
            输出单元
            输出单元向混音器连续输出 7 位数值。它包含一个 8 位右移寄存器、一个比特剩余计数器、一个 7 位输出电平（与可通过 4011 美元直接加载的电平相同）和一个静音标志。

            只要定时器输出一个时钟，比特剩余计数器就会更新，而不管当前是否正在播放采样。当计数器的位数为零时，输出周期结束。只有在输出周期结束时，DPCM 单元才能从静音转换为播放。

            当一个输出周期结束时，新的周期将按如下方式开始：

            剩余比特计数器加载 8。
            如果采样缓冲器为空，则设置静音标志；否则清除静音标志，并将采样缓冲器清空，存入移位寄存器。
            当定时器输出时钟时，将依次进行以下操作：

            如果静音标志清除，则根据移位寄存器的第 0 位改变输出电平。如果该位为 1，则加 2；否则减 2。但如果加 2 或减 2 会导致输出电平超出 0-127 范围，则保持输出电平不变。也就是说，只有当当前电平至少为 2 时才减 2，或者只有当当前电平最多为 125 时才加 2。
            右移寄存器采用时钟控制。
            如上所述，比特剩余计数器被递减。如果计数器的位数为零，则开始一个新的输出周期。
            没有任何东西可以中断一个周期；每个周期运行完成后才开始新的周期。
         */
        //移位寄存器
        private int shifter;
        //剩余比特位计数器
        private int bitCount = 8;

        private int timer = 0;
        private boolean muting = false;//静音标志

        /*
            采样率表
         */
        private int[] rateTable = {428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54};//NTSC
//        private int[] rateTable = {398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118,  98,  78,  66,  50};//PAL


        public void setChannelEnabled(boolean channelEnabled) {
            super.setChannelEnabled(channelEnabled);
            if (channelEnabled) {
                dmcMemoryReader.load();
            } else {
                dmcMemoryReader.dmcIirqDevice.acknowledge();
            }
        }

        @Override
        public void clock() {
            timer++;
            if (timer % rateTable[rateIndex] == 0) {
                step();
            }
        }

        public void step() {
            if (muting) {
                volume = 0;
            } else {
                outLevel += (shifter & 0x01) == 0 ? outLevel > 1 ? -2 : 0 : outLevel < 126 ? 2 : 0;
                volume = outLevel;
                shifter >>= 1;

            }
            bitCount--;
            if (bitCount == 0) {
                newOutCycle();
            }
        }

        private void newOutCycle() {
            bitCount = 8;
            if (dmcMemoryReader.sampleBuffer == -1) {
                muting = true;
            } else {
                muting = false;
                shifter = dmcMemoryReader.clearSampleBuffer();
            }
        }

        public boolean playing() {
            return isChannelEnabled() && !muting;
        }
    }

    private class TriangleChannel extends BaseChannel implements ClockDevice {
        private int lineCounterReload;//线性计数器重载值
        private int lineCounter;//线性计数器

        private boolean controlFlag;//控制标值，true允许线性计数器，不允许长度计数器
        private boolean reloadFlag;//重载标值
        private int[] sequence = {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};


        public void setLineCount(int data) {
            this.controlFlag = (data & 0x80) > 0;
            reloadFlag = controlFlag;
            lineCounterReload = data & 0x7F;
        }

        public void step(int stepCounter) {
            /*
                Unlike the pulse channels, the triangle channel supports frequencies up to the maximum frequency the timer will allow, meaning frequencies up to fCPU/32 (about 55.9 kHz for NTSC) are possible - far above the audible range. Some games, e.g. Mega Man 2, "silence" the triangle channel by setting the timer to zero, which produces a popping sound when an audible frequency is resumed, easily heard e.g. in Crash Man's stage. At the expense of accuracy, these can be eliminated in an emulator e.g. by halting the triangle channel when an ultrasonic frequency is set (a timer value less than 2).
                Other games, e.g. Zombie Nation and Bullet-Proof Software's Tetris, "silence" the triangle channel by setting the timer to $7FF, which produces a deep rumble and quiet whine.
                与脉冲通道不同，三角形通道支持的频率最高可达定时器允许的最大频率，这意味着频率最高可达 fCPU/32（NTSC 制式约为 55.9 kHz）--远高于可听范围。有些游戏，如《洛克人 2》，通过将计时器设置为零来 "静音 "三角通道，当恢复可听频率时会产生 "啪啪 "的声音，例如在 "崩溃人 "的舞台上很容易听到。在模拟器中，可以通过设置超声波频率（定时器值小于 2）时停止三角通道等方法消除这些问题，但会牺牲精确度。

                The sequencer is clocked by the timer as long as both the linear counter and the length counter are nonzero.
                只要线性计数器和长度计数器都不为零，定时器就会为定序器提供时钟。
             */
            if (!isChannelEnabled() || lineCounter == 0 || lengthCounter == 0 || timer < 2 || timer >= 0x7FF) {
                volume = 0;
            } else {
                volume = sequence[stepCounter & 0x1F];
            }
        }

        public boolean playing() {
            return isChannelEnabled() && (lengthCounter > 0||lineCounter>0);
        }

        /**
         * 向 4008 美元写入 80 美元，将使线性计数器停止运行，并在下一帧序列 tick 时重新加载 0。只需将 $FF 写入 $4008 即可恢复。(使用此方法时，控制标志和重新加载标志应永久设置）。
         * 可选项：如果不希望帧序列有 1/4 帧的延迟，在向 4008 写入 $FF 之后再向 $4017 写入，可立即触发帧序列 tick。不过，这也会产生帧序列 tick 的所有其他后果。
         */
        public void processLineCounter() {
            if (reloadFlag) {
                lineCounter = lineCounterReload;
                reloadFlag = false;
            }
            if (lineCounter > 0) {
                lineCounter--;
            }
        }

        public void processLengthCounter() {
            if (controlFlag) {
                return;
            }
            if (lengthCounter > 0) {
                lengthCounter--;
            }
        }

        @Override
        public void setLengthCounter(int data) {
            reloadFlag = true;
            super.setLengthCounter(data);
        }
    }

    /**
     * bit:  14 13 12 11 10 9 8 7 6 5 4 3 2 1     0
     * ^                   |         |     |
     * |                   v         v     |
     * |                 \"1"""""""""0"/   |
     * |     $400E.7 ---->\    Mux    /    |
     * |                   \_________/     |
     * |                        |          |
     * |       /"""""//<--------'          |
     * `------( XOR ((                     |
     * \_____\\<-------------------'
     * <p>
     * 噪声通道的线性反馈移位寄存器（LFSR）的工作原理和特性。
     * <p>
     * --移位寄存器：移位寄存器是15位宽，位编号从0到14。
     * <p>
     * --反馈计算：当定时器驱动移位寄存器时，首先计算反馈值。反馈值是位0和另一位的异或（XOR）结果。如果模式标志（Mode flag）被设置，这个另一位是位6；否则，这个另一位是位1。
     * <p>
     * --右移：然后，移位寄存器向右移动一位。
     * <p>
     * --设置位14：位14（最左边的位）被设置为之前计算的反馈值。
     * <p>
     * 这个过程生成了一个伪随机的位序列，长度为32767步，如果模式标志被清除；否则，长度随机为93步或31步。具体的31步或93步序列取决于设置模式标志时移位寄存器在32767步序列中的位置。
     * <p>
     * 当满足以下条件时，混音器（mixer）不会接收当前的包络音量：
     * --移位寄存器的位0被设置，或长度计数器为零
     * <p>
     * 在混音器内部，DMC级别会明显影响噪声的级别。
     * 在上电时，移位寄存器被加载为值1。
     * <p>
     * 在2A03 CPU的最早版本中，模式标志不存在，移位寄存器总是使用位0和位1作为反馈。这些CPU被用在第一批Famicom游戏机、Vs. System主板和使用2A03作为声音协处理器的街机游戏中。
     * 93步序列相对于A440调音约为四分之一音高（50美分）。具体的频率和音高（以LilyPond的Helmholtz记号表示）如下：（此处应有表格或列表，但原文未给出）。
     */
    private class NoiseChannel extends EnvelopeChannel {

        //NTSC timer表
        private int[] periodTable = {4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068};
        //PAL timer表
//        private int[] periodTable ={4, 8, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708,  944, 1890, 3778};

        //移位寄存器
        //On power-up, the shift register is loaded with the value 1.
        private int shiftRegister = 1;
        private boolean mode;//模式标值

        public void setPeriod(int data) {
            this.timer = periodTable[data & 0x0F];
            mode = (data & 0x80) > 0;
            this.internalTimer = 0;
        }

        public void processLengthCounter() {
            if (!isEnvelopeLoop && lengthCounter > 0) {
                lengthCounter--;
            }
        }

        @Override
        public void step(int stepCounter) {
            shiftRegister();//移动位移寄存器
            if (!isChannelEnabled() || lengthCounter == 0) {
                volume = 0;
            } else if (isConstantVolume) {
                volume = envelopeValue * (shiftRegister & 0x01);
            } else {
                volume = decayLevel * (shiftRegister & 0x01);
            }
        }

        /**
         * 移动移位寄存器
         */
        public void shiftRegister() {
            int a;
            if (mode) {//如果模式被设置
                a = (shiftRegister & 0x40) >> 6;//移位寄存器中0bit位与6bit位异或
            } else {
                a = (shiftRegister & 0x02) >> 1;//否则移位寄存器中0bit位与1bit位异或
            }
            a ^= (shiftRegister & 0x01);
            shiftRegister >>= 1;
            shiftRegister |= (a << 14);
        }
    }

    /**
     * 方波通道
     *
     * @link [https://www.nesdev.org/wiki/APU_Pulse]
     */
    private class PulseChannel extends EnvelopeChannel implements ClockDevice {

        private int channel;//方波编号
        private boolean isSweepEnabled;//是否允许滑音
        private int sweepPeriod;//滑音频率幅度
        private boolean isSweepNegated;//滑音负标志位
        private int sweepShift;
        private int sweepCounter;
        //已生成的样本数量
        private int duty = 0;//空占比，对应dutyTable下标

        public PulseChannel(int channel) {
            this.channel = channel;
        }

        /**
         * 设占空比和音量/包络
         *
         * @param data
         */
        public void setDutyAndVolumeByEnvelope(int data) {
            duty = (data & 0xC0) >> 6;//空占比索引
            setVolumeByEnvelope(data);
        }

        public void step(int stepCounter) {
            /*
                The sequencer output is zero, or
                overflow from the sweep unit's adder is silencing the channel, or
                the length counter is zero, or
                the timer has a value less than eight (t<8, noted above).
                If any of the above are true, then the pulse channel sends zero (silence) to the mixer.

                定序器输出为零，或
                扫频单元的加法器溢出使通道静音，或
                长度计数器为零，或
                定时器值小于 8（t<8，如上所述）。
                如果上述任一情况为真，则脉冲通道向混音器发送零（静音）信号。
             */
            if (!isChannelEnabled() || lengthCounter == 0 || timer < 8 || timer > 0x7FF) {
                volume = 0;
            }
            else if (isConstantVolume) {
                volume = envelopeValue * dutyTable[duty][stepCounter & 0x07];
            } else {
                volume = decayLevel * dutyTable[duty][stepCounter & 0x07];
            }
        }

        @Override
        public boolean playing() {
            return isChannelEnabled() && lengthCounter > 0;
        }

        //----------------------------滑音相关------------------------------------------

        /**
         * 设置滑音相关操作
         * <p>
         * data
         * EPPP.NSSS
         * bit 7	E--- ----	Enabled flag
         * 已启用标志
         * bits 6-4	-PPP ----	The divider's period is P + 1 half-frames
         * 分频器的周期为 P + 1 个半帧
         * bit 3	---- N---	Negate flag
         * 0: add to period, sweeping toward lower frequencies
         * 1: subtract from period, sweeping toward higher frequencies
         * 负标志
         * 0：与周期相加，向低频扫描
         * 1：与周期相减，向高频扫描
         * bits 2-0	---- -SSS	Shift count (number of bits).
         * If SSS is 0, then behaves like E=0.
         * 移位计数（位数）。
         * 如果 SSS 为 0，则表现为 E=0。
         *
         * @param data
         */
        public void setSweep(int data) {
            //是否开启滑音效果
            isSweepEnabled = (data & 0x80) > 0;
            sweepPeriod = (data & 0x70) >> 4;
            isSweepNegated = (data & 0x08) > 0;
            sweepShift = (data & 0x07);
            sweepCounter = 0;
        }

        /**
         * 处理滑音
         * <p>
         * Calculating the target period
         * The sweep unit continuously calculates each pulse channel's target period in this way:
         * <p>
         * A barrel shifter shifts the pulse channel's 11-bit raw timer period right by the shift count, producing the change amount.
         * If the negate flag is true, the change amount is made negative.
         * The target period is the sum of the current period and the change amount, clamped to zero if this sum is negative.
         * For example, if the negate flag is false and the shift amount is zero, the change amount equals the current period, making the target period equal to twice the current period.
         * <p>
         * The two pulse channels have their adders' carry inputs wired differently, which produces different results when each channel's change amount is made negative:
         * <p>
         * Pulse 1 adds the ones' complement (−c − 1). Making 20 negative produces a change amount of −21.
         * Pulse 2 adds the two's complement (−c). Making 20 negative produces a change amount of −20.
         * Whenever the current period or sweep setting changes, whether by $400x writes or by sweep updating the period, the target period also changes.
         * 计算目标期限
         * 扫描单元以这种方式连续计算每个脉冲通道的目标周期：
         * <p>
         * 桶式移位器将脉冲通道的 11 位原始计时器周期按移位计数向右移位，产生变化量。
         * 如果否定标志为真，则变化量为负。
         * 目标周期是当前周期与变化量之和，如果该和为负，则箝位为零。
         * 例如，如果否定标志为假，且变化量为零，则变化量等于当前周期，使目标周期等于当前周期的两倍。
         * <p>
         * 两个脉冲通道的加法器进位输入端接线方式不同，当每个通道的变化量为负时，会产生不同的结果：
         * <p>
         * 脉冲 1 加 1 的补码（-c - 1）。将 20 设为负数会产生 -21 的变化量。
         * <p>
         * <p>
         * ---------------------------------------------------------------------------------------
         * <p>
         * <p>
         * Updating the period
         * When the frame counter sends a half-frame clock (at 120 or 96 Hz), two things happen:
         * <p>
         * If the divider's counter is zero, the sweep is enabled, the shift count is nonzero,
         * and the sweep unit is not muting the channel: The pulse's period is set to the target period.
         * and the sweep unit is muting the channel: the pulse's period remains unchanged, but the sweep unit's divider continues to count down and reload the divider's period as normal.
         * If the divider's counter is zero or the reload flag is true: The divider counter is set to P and the reload flag is cleared. Otherwise, the divider counter is decremented.
         * If the sweep unit is disabled including because the shift count is zero, the pulse channel's period is never updated, but muting logic still applies.
         * <p>
         * 更新时间段
         * 当帧计数器发送半帧时钟（120 或 96 Hz）时，会发生两种情况：
         * <p>
         * 如果分频器的计数器为零，则启用扫频，移位计数不为零、
         * 并且扫频单元没有静音通道： 脉冲周期被设置为目标周期。
         * 扫频单元正在静音通道：脉冲周期保持不变，但扫频单元的分频器继续倒计时，并按正常方式重新加载分频器的周期。
         * 如果分频器计数器为零或重载标志为真：则分频器计数器置 P，重载标志清零。否则，除法器计数器递减。
         * 如果扫描单元被禁用，包括移位计数为零，则脉冲通道的周期永不更新，但静音逻辑仍然适用。
         */
        public void processSweep() {
            if (!isSweepEnabled||sweepShift==0) {
                return;
            }
            if (sweepCounter % (sweepPeriod + 1) == 0) {
                timer += isSweepNegated ? -(timer >> sweepShift) : timer >> sweepShift;
                if (channel == PulseChannel_1 && isSweepNegated) {
                    timer--;//方波通道1的滑音递减时多减1
                }
                if(timer<0){
                    timer = 0;
                }
            }
            sweepCounter++;
        }

        public void setLengthCounter(int data) {
            stepCounter = 0;
            super.setLengthCounter(data);
        }
    }

    /**
     * In a synthesizer, an envelope is the way a sound's parameter changes over time. The NES APU has an envelope generator that controls the volume in one of two ways: it can generate a decreasing saw envelope (like a decay phase of an ADSR) with optional looping, or it can generate a constant volume that a more sophisticated software envelope generator can manipulate. Volume values are practically linear (see: APU Mixer).
     * Each volume envelope unit contains the following: start flag, divider, and decay level counter.
     * <p>
     * 在合成器中，包络是声音参数随时间变化的方式。NES APU 有一个包络发生器，可通过两种方式之一控制音量：它可以生成一个递减锯形包络（就像 ADSR 的衰减阶段），并可选择循环播放；或者生成一个恒定音量，由更复杂的软件包络发生器进行处理。音量值实际上是线性的（参见：APU 混音器）。
     * 每个音量包络单元都包含以下部分：起始标志、分频器和衰减电平计数器。
     * <p>
     * 支持包络的通道
     * <p>
     * Divider:分频器
     * Envelope:包络
     * Decay:衰变
     * <p>
     * Loop flag
     * |
     * Start flag  +--------.   |   Constant volume
     * |        |   |        flag
     * v        v   v          |
     * Quarter frame clock --> Divider --> Decay --> |    |
     * ^        level     |    v
     * |                  | Select --> Envelope output
     * |                  |
     * Envelope parameter +----------------> |
     * <p>
     * <p>
     * <p>
     * <p>
     * 在NES APU（音频处理单元）中，写入寄存器的值通常被编码为一个8位二进制数。对于包络单元来说，这个值通常被表示为--LC.VVVV，其中：
     * <p>
     * 写入寄存器的数据:--LC VVVV
     * --：这两位通常不被使用，所以它们被忽略。
     * L：这一位表示循环标志（Loop flag）。如果它被设置（即值为1），那么当衰减级别为0时，衰减级别会被重置为15，从而创建一个循环的包络。
     * C：这一位表示常量音量标志（Constant volume flag）。如果它被设置（即值为1），那么包络输出将为包络参数值，而不是衰减级别。这可以用于创建一个不会衰减的音量输出。
     * VVVV：这四位表示包络参数（Envelope parameter）。这个值用于设置分频器的初始值，以及在常量音量模式下的输出音量。
     * 这个编码方式允许通过一个8位的值来控制包络单元的行为，包括音量的衰减速度，是否循环，以及是否输出常量音量。
     * <p>
     * <p>
     * 当帧计数器驱动时，如果开始标志（Start Flag）未设置，分频器（Divider）被驱动；如果开始标志设置了，那么开始标志被清除，衰减级别计数器（Decay Level Counter）被加载为15，分频器的周期立即重新加载。这与工作流程图中的"Quarter Frame Clock"和"Envelope Parameter"阶段相对应。
     * <p>
     * 当分频器在0时被驱动，它被加载为V（包络参数），并驱动衰减级别计数器。然后发生两种情况之一：如果计数器非零，它被递减；否则，如果循环标志（Loop Flag）设置，衰减级别计数器被加载为15。这与工作流程图中的"Divider"和"Decay"阶段相对应。
     * <p>
     * 包络单元的音量输出取决于常量音量标志（Constant Volume Flag）：如果设置，包络参数直接设置音量；否则，衰减级别是当前音量。常量音量标志除了选择音量源外没有其他影响；当选择常量音量时，衰减级别仍将更新。这与工作流程图中的"Select"阶段相对应。
     * <p>
     * 每个包络单元的输出都通过附加的门在扫频单元（仅限脉冲）、波形发生器（顺序器或LFSR）和长度计数器进行。这部分不在我们之前的工作流程图中，但它表示的是输出音频信号在生成后进一步处理的过程。
     *
     * @link [https://www.nesdev.org/wiki/APU_Envelope]
     */
    public abstract class EnvelopeChannel extends BaseChannel {
        protected boolean isEnvelopeLoop;//是否循环包络(包络音量为0时，自动回到包络初始音量)
        protected boolean isConstantVolume;//是否固定音量
        protected int envelopeValue;//在恒定音量（isConstantVolume设置）模式下用作音量，包络周期
        protected int decayLevel;//包络音量(衰减级别)

        private int envelopeCounter;

        /**
         * 处理包络
         * <p>
         * When clocked by the frame counter, one of two actions occurs: if the start flag is clear, the divider is clocked, otherwise the start flag is cleared, the decay level counter is loaded with 15, and the divider's period is immediately reloaded.
         * <p>
         * When the divider is clocked while at 0, it is loaded with V and clocks the decay level counter. Then one of two actions occurs: If the counter is non-zero, it is decremented, otherwise if the loop flag is set, the decay level counter is loaded with 15.
         * <p>
         * The envelope unit's volume output depends on the constant volume flag: if set, the envelope parameter directly sets the volume, otherwise the decay level is the current volume. The constant volume flag has no effect besides selecting the volume source; the decay level will still be updated when constant volume is selected.
         * <p>
         * Each of the three envelope units' output is fed through additional gates at the sweep unit (pulse only), waveform generator (sequencer or LFSR), and length counter.
         * <p>
         * <p>
         * 当帧计数器计时时，会发生以下两种情况之一：如果起始标志清除，则除法器计时，否则起始标志清除，衰减电平计数器加载 15，除法器周期立即重新加载。
         * <p>
         * 当分频器的时钟周期为 0 时，它将加载 V，并为衰减电平计数器计时。然后会发生两种情况之一：如果计数器不为零，则将其递减，否则，如果设置了循环标志，则将衰减电平计数器加载到 15。
         * <p>
         * 包络单元的音量输出取决于恒定音量标志：如果设置了恒定音量标志，包络参数将直接设置音量，否则衰减电平就是当前音量。除了选择音量源之外，恒定音量标志没有任何作用；选择恒定音量时，衰减电平仍会更新。
         */
        public void processEnvelope() {
            envelopeCounter--;
            if (envelopeCounter == 0) {
                envelopeCounter = envelopeValue + 1;
                if (decayLevel == 0) {
                    decayLevel = isEnvelopeLoop ? 15 : 0;
                } else {
                    decayLevel--;
                }
            }
        }

        /**
         * 处理长度计数器
         */
        public void processLengthCounter() {
            if (!isEnvelopeLoop && lengthCounter > 0) {
                lengthCounter--;
            }
        }

        /**
         * 设置包络数据
         * <p>
         * bit 5	--L- ----	APU Length Counter halt flag/envelope loop flag
         * APU 长度计数器停止标志/包络循环标志
         * <p>
         * bit 4	---C ----	Constant volume flag (0: use volume from envelope; 1: use constant volume)
         * 恒定音量标志（0：使用包络音量；1：使用恒定音量）
         * <p>
         * bits 3-0	---- VVVV	Used as the volume in constant volume (C set) mode. Also used as the reload value for the envelope's divider (the period becomes V + 1 quarter frames).
         * 在恒定音量（C 设置）模式下用作音量。也用作包络分频器的重载值（周期变为 V + 1 个四分帧）。
         *
         * @param data --LC.VVVV
         */
        public void setVolumeByEnvelope(int data) {
            isEnvelopeLoop = (data & 20) > 0;//是否循环播放，循环则不
            isConstantVolume = (data & 0x10) > 0;//是否允许包络,否则为固定音量
            envelopeValue = data & 0x0F;//在恒定音量（C 设置）模式下用作音量，包络周期

            //下面相当于start状态，此时下一个帧计数器处理包络时，看到的就是重置后的值
            decayLevel = 15;
            envelopeCounter = envelopeValue + 1;//也用作包络分频器的重载值（周期变为 V + 1 个四分帧）。
        }

        public void resetEnvelope() {
            //下面相当于start状态，此时下一个帧计数器处理包络时，看到的就是重置后的值
            decayLevel = 15;
            isEnvelopeLoop = false;
            envelopeCounter = envelopeValue + 1;//也用作包络分频器的重载值（周期变为 V + 1 个四分帧）。
        }

        public void setLengthCounter(int data) {
            resetEnvelope();
            super.setLengthCounter(data);
        }
    }

    /**
     * 通道基类，定义所有通道共有属性
     */
    public abstract class BaseChannel extends Chanel {


        protected int timer;
        protected int internalTimer;

        protected int stepCounter;//步进计数器

        /*
            link:https://www.nesdev.org/wiki/APU_Length_Counter

            The length counter provides automatic duration control for the NES APU waveform channels. Once loaded with a value, it can optionally count down (when the length counter halt flag is clear).
            Once it reaches zero, the corresponding channel is silenced.

            长度计数器为 NES APU 波形通道提供自动持续时间控制。加载数值后，
            它可以选择倒计时（当长度计数器停止标志清零时）。一旦达到零，相应通道将被静音。
         */
        protected int lengthCounter = 0;//长度计数器

        @Override
        public void clock() {
            if (!isChannelEnabled()) {
                return;
            }
            if (internalTimer == 0) {
                internalTimer = timer;
                step(stepCounter++);
            } else {
                internalTimer--;
            }
        }

        public void setChannelEnabled(boolean channelEnabled) {
            super.setChannelEnabled(channelEnabled);
            //当通道不可用时，长度计数器被置0
            if (!isChannelEnabled()) {
                lengthCounter = 0;
            }
        }

        /**
         * 计时器步进操作
         */
        public abstract void step(int stepCounter);

        public void setLengthCounter(int data) {
            lengthCounter = lengthTable[(data & 0xF8) >> 3];
        }

        /**
         * 设置定时器
         *
         * @param data 定时器数据
         * @param low  是否设置低位否则将data最后3bit作为timer高位  timer:HHH LLLLLLLL
         */
        public void setTimer(int data, boolean low) {
            if (low) {
                timer = timer & 0xFF00 | data;
            } else {
                timer = (data & 0x07) << 8 | timer & 0x00FF;
            }
            internalTimer = 0;
            stepCounter = 0;
        }

        public boolean playing() {
            return isChannelEnabled() && lengthCounter > 0;
        }

    }

    private abstract class Chanel implements ClockDevice, Serializable {
        protected boolean channelEnabled;//当前通道是否允许使用
        protected int volume;//音量0-15

        /**
         * 获取采样
         *
         * @return
         */
        public int nextSampling() {
            return volume;
        }


        public boolean isChannelEnabled() {
            return channelEnabled;
        }

        public void setChannelEnabled(boolean channelEnabled) {
            this.channelEnabled = channelEnabled;
        }

        public abstract boolean playing();
    }

    private DmcMemoryReader dmcMemoryReader = new DmcMemoryReader();

    public class DmcMemoryReader extends DmaDevice {
        /*
            8bit 采样缓冲寄存器
            采样缓冲器要么保存一个 8 位采样字节，要么是空的。它由读取器填满，只能由输出单元清空；一旦装入一个采样字节，就会进行回放。
         */
        private int sampleBuffer = -1;

        //---------------MemoryReader------------------
        /*
            当采样（重新）开始时，当前地址将被设置为采样地址，剩余字节将被设置为采样长度。

            当采样缓冲区处于空状态且剩余字节不为零时（包括刚刚向 4015 美元写入使能通道的数据后，无论该写入相对于下面提到的位计数器发生在何处），会发生以下情况：
            CPU 会停滞 1-4 个 CPU 周期以读取一个样本字节。具体周期数取决于很多因素，在 DMA 一文中有详细描述。
            采样缓冲区将被从当前地址读取的下一个采样字节填满，这取决于现有的映射硬件。
            地址会递增；如果超过了$FFFF，则会绕到$8000。
            剩余字节计数器递减；如果它变为零且循环标志被设置，则重新开始采样（见上文）；否则，如果剩余字节计数器变为零且启用 IRQ 标志被设置，则中断标志被设置。
            在任何时候，如果中断标志被设置，CPU 的 IRQ 线路都会持续断言，直到中断标志被清除。处理器将从停顿的位置继续运行。
         */
        private int sampleAddress;
        //当前采样地址
        private int currentAddress;
        //采样长度
        private int sampleLength;
        //剩余字节计数器
        private int remainingCounter;

        private boolean transfer = false;//需要DMA传输
        private int cycleCount;
        public boolean ready = false;

        public boolean isTransfer(int cpuCycle) {
            if (transfer) {
                cycleCount++;
                if (cycleCount > 2&&cpuCycle % 2 == 0) {
                    ready = true;
                }
            }
            return transfer;
        }

        public void transfer() {
            if (!ready) {
                return;
            }
            if (remainingCounter > 0) {
                if (sampleBuffer == -1) {
                    sampleBuffer = fromBusRead(currentAddress);
                    transfer = false;
                    currentAddress++;
                    if (currentAddress > 0xFFFF) {
                        currentAddress = 0x8000;
                    }
                    remainingCounter--;
                }
            } else {
                if (dmcChannel.loop) {
                    reload();
                } else {
                    dmcIirqDevice.irq();
                }
            }
        }

        public int clearSampleBuffer() {
            int value = sampleBuffer;
            sampleBuffer = -1;
            if (remainingCounter > 0) {
                transfer = true;
                cycleCount = 0;
                ready = false;
            }
            return value;
        }

        /*
            加载 DMA 在 $4015 D4 设置后发生，但仅限于采样缓冲区为空的情况。它们计划在写入后的第二个 APU 周期（即第 3 或第 4 个 CPU 周期）内的获取周期上停止 CPU。
         */
        public void load() {
            if (sampleBuffer != -1) {
                return;
            }
            currentAddress = sampleAddress;
            remainingCounter = sampleLength;
            transfer = true;
            cycleCount = 0;
            ready = false;
        }

        /**
         * 重新加载 DMA 会在采样缓冲区清空时发生。与加载 DMA 不同的是，重加载 DMA 会在写入周期内停止 CPU 运行。
         */
        public void reload() {
            dmcMemoryReader.dmcIirqDevice.acknowledge();//重新播放另一段采样确认中断
            currentAddress = sampleAddress;
            remainingCounter = sampleLength;
            sampleBuffer = -1;
            transfer = true;
            cycleCount = 0;
            ready = false;
        }

        private IRQLineDevice dmcIirqDevice = new IRQLineDevice();

        public DmcMemoryReader() {
            IRQLine.getInstance().addDevice(dmcIirqDevice);
        }
    }

    public DmcMemoryReader getDma() {
        return dmcMemoryReader;
    }
}
